//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  Low level interfaces that allow separate components to use objects that are defined
//               in completely independant libraries so long as they satisfy the basic interface requirements.
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either express or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is MapWindow.Interfaces
//
// The Initial Developer of this Original Code is Ted Dunsford. Created in September, 2007.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using MapWindow.Main.EventArguments;
using MapWindow.Data;
using MapWindow.Geometries;
namespace MapWindow.Drawing
{
    /// <summary>
    /// Allows for the construction of a specific type of LineFeatureSymbology that consists of
    /// two lines technically, a wider, fatter background that becomes the border and a non-transparent 
    /// fill
    /// </summary>
    public class MapLineString : MapFeature, IMapLineString
    {
        #region Variables

        // The drawing characteristics represented here should not be accessed directly.
        // They are overrides for the default which lies at the layer level and should
        // be used only when a property has to be different from the layer properties.
        // the public property checks for the layer characteristics.
        /// <summary>
        /// A cached list of floating points for faster drawing
        /// </summary>
        protected List<PointF[]> _floatPointArrays;

        /// <summary>
        /// The parent layer
        /// </summary>
        protected IMapLineStringLayer _ParentMapLineStringLayer;

        /// <summary>
        /// Pixels or world units for width
        /// </summary>
        protected ScaleModes _ScaleMode;

        /// <summary>
        /// Won't draw if false
        /// </summary>
        protected bool _Visible;

        /// <summary>
        /// Drawing characteristics for this specific linestring
        /// </summary>
        protected ILineSymbolizer _LineSymbolizer;

        /// <summary>
        /// A list of parts for multipart linestrings
        /// </summary>
        protected List<IBasicLineString> _Parts;
       
        
        
        
       

        #endregion

        #region Constructors

        /// <summary>
        /// This creates a new map point from the specified point feature with
        /// null characteristics and the IsEmpty property set to true.
        /// </summary>
        /// <param name="fromFeature">The point feature to create into a MapPoint</param>
        public MapLineString(IFeature fromFeature) : base(fromFeature)
        {
            //_Position = new PointF(Convert.ToSingle(fromFeature.BasicGeometry.Coordinates[0].X), Convert.ToSingle(fromFeature.BasicGeometry.Coordinates[0].Y));
            //_BasicPoint = (IBasicPoint)fromFeature.BasicGeometry;
            _Parts = new List<IBasicLineString>();

            if (fromFeature != null)
            {
                if (fromFeature.FeatureType != FeatureTypes.Line)
                {
                    throw new InvalidOperationException("Cannot create a MapLineString from geometries that have a feature type other than line.");
                }
                if (fromFeature.BasicGeometry != null)
                {
                    // Use a safe cast to see if we have a multi-geometry or not
                    IBasicLineString bl = fromFeature.BasicGeometry as IBasicLineString;
                    if (bl == null)
                    {
                        // try the multi-linestring approach
                        for (int iPart = 0; iPart < fromFeature.BasicGeometry.NumGeometries; iPart++)
                        {
                            _Parts.Add((IBasicLineString)fromFeature.BasicGeometry.GetBasicGeometryN(iPart));
                        }
                    }
                    else
                    {
                        _Parts.Add(bl);
                    }
                }
            }
         
            _IsBusy = false;
            _Visible = true;
            //Clear();
        }

        /// <summary>
        /// Constructs a linestring from a feature using a default set of symbol rules
        /// </summary>
        /// <param name="fromFeature">the linestring with the data elements</param>
        /// <param name="defaultSymbolizer">the symbol rules </param>
        public MapLineString(IFeature fromFeature, ILineSymbolizer defaultSymbolizer)
            : base(fromFeature, defaultSymbolizer)
        {
            _LineSymbolizer = defaultSymbolizer;
            _IsBusy = false;
            _Parts = new List<IBasicLineString>();

            if (fromFeature != null)
            {
                if (fromFeature.FeatureType != FeatureTypes.Line)
                {
                    throw new InvalidOperationException("Cannot create a MapLineString from geometries that have a feature type other than line.");
                }
                if (fromFeature.BasicGeometry != null)
                {
                    // Use a safe cast to see if we have a multi-geometry or not
                    IBasicLineString bl = fromFeature.BasicGeometry as IBasicLineString;
                    if (bl == null)
                    {
                        // try the multi-linestring approach
                        for (int iPart = 0; iPart < fromFeature.BasicGeometry.NumGeometries; iPart++)
                        {
                            _Parts.Add((IBasicLineString)fromFeature.BasicGeometry.GetBasicGeometryN(iPart));
                        }
                    }
                    else
                    {
                        _Parts.Add(bl);
                    }
                }
            }
        }

        #endregion

        #region Methods

       

        /// <summary>
        /// Copies the drawing characteristics from the specified ILineSymbolizer
        /// </summary>
        /// <param name="lineSymbolizer"></param>
        public void Copy(ILineSymbolizer lineSymbolizer)
        {
            _LineSymbolizer = lineSymbolizer;
        }

       


     

        /// <summary>
        /// This simply calls the draw function for each of its specific features, if it has them defined,
        /// or else it calls the default characteristics.
        /// TO DO: Support Symbols based on attributes
        /// </summary>
        /// <param name="g"></param>
        /// <param name="clipRect"></param>
        /// <param name="target"></param>
        public override void Draw(System.Drawing.Graphics g, System.Drawing.Rectangle clipRect, System.Drawing.Rectangle target)
        {
            List<Pen> ScalePens = new List<Pen>(); // A temporary copy of the drawing pens to adjust scale
            float minScale = 1;
            

            try
            {
                // show that we are actively drawing
                _IsBusy = true;
                
                if (this.Visible == false)
                {
                    // We won't get here if this was intercepted in a previous draw method.
                    OnPaintCancelled(new OperationCanceledException("Visible was false"), g, clipRect, target);
                    return;
                }

               

                // The OnBeforePainting method can cancel the drawing
                if (OnBeforePainting(g, clipRect, target) == true)
                {
                    OnPaintCancelled(new OperationCanceledException("Canceled by OnBeforePainting"), g, clipRect, target);
                    return;
                }

                // Many times an Envelope might not be defined yet, so this will simply exit in that case
                if (Envelope == null)
                {
                    OnAfterPainting(g, clipRect, target);
                    return;
                }

                if (Envelope.IsNull)
                {
                    OnAfterPainting(g, clipRect, target);
                    return;
                }

                // At this point, if a clip region is specified, apply it before any Transforms
                if (clipRect.IsEmpty == false)
                {
                    g.Clip = new Region(clipRect);
                }

                // Keep a backup in case we need to make changes to the transform
                System.Drawing.Drawing2D.Matrix oldMat = g.Transform;


                // Create a Transform if a target exists, otherwise, just use the existing transform
                if (target.IsEmpty == false)
                {
                    // Before applying any transforms, modify the clip region
                    g.IntersectClip(target);

                    // Create a transform in order to fit the Envelope into the Target
                    System.Drawing.Drawing2D.Matrix mat = new System.Drawing.Drawing2D.Matrix();


                    // While we are in pixel coordinates, translate to the specified Frame
                    mat.Translate((float)target.X, (float)target.Y);

                    // Scaling 
                    // An envelope width or height of 0 will throw an exception.
                    // I think that is better than simply not drawing, since it helps track the problem
                    float xScale = (float)(target.Width / Envelope.Width);
                    float yScale = (float)(target.Height / Envelope.Height);
                    minScale = Math.Min(xScale, yScale);

                    // Make yScale negative because in drawing space Y gets larger as you go down, but
                    // in world coordinates, Y gets larger as you go up.
                    mat.Scale(xScale, -yScale);

                    // Now that we are in world coordinates, pull the world envelope into view
                    // The top left corner is defined by the Minimum X and the Maximum Y
                    mat.Translate(-(float)Envelope.MinX, -(float)Envelope.MaxY);

                    // I control the transform this way because I can't seem to apply methods to the 
                    // graphics transform directly.
                    g.Transform = mat;

                }
                else
                {
                    // assume that a transformation has occured and that we need to calculate minScale from the matrix
                    float xScale = Math.Abs((float)(g.Transform.Elements[0]));
                    float yScale = Math.Abs((float)(g.Transform.Elements[3]));
                    minScale = Math.Min(xScale, yScale);
                }
               

                if(ScaleMode == ScaleModes.Pixels)
                {
                    Pen borderCopy = (Pen)BorderPen.Clone();
                    borderCopy.Width = borderCopy.Width / minScale;
                    ScalePens.Add(borderCopy);
                    foreach (Pen p in FillPens)
                    {
                       Pen copy = (Pen)p.Clone();
                       copy.Width = copy.Width / minScale;
                       ScalePens.Add(copy);
                    }
                }
                else
                {
                    ScalePens.Add(BorderPen);
                    ScalePens.AddRange(FillPens);
                }

               

                // Actually Draw the shape
                foreach (Pen CurrentPen in ScalePens)
                {
                    foreach (PointF[] Part in floatPointArrays)
                    {
                        g.DrawLines(CurrentPen, Part);   
                    }
                }

                // Set the trasform back to whatever it was before we drew the line string
                if(target.IsEmpty == false) g.Transform = oldMat;
                
            }
            catch (Exception ex)
            {
                OnPaintCancelled(ex, g, clipRect, target);
            }
            OnAfterPainting(g, clipRect, target);
        }

      
        /// <summary>
        /// Gets the Nth coordinate from the list of coordinates for this linestring
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public ICoordinate GetCoordinateN(int n)
        {
            return _BasicGeometry.Coordinates[n];
        }


       

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the color of the border, or outline for this point.
        /// If this is empty, the default for the layer will be used.
        /// </summary>
        public override System.Drawing.Color BorderColor
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.BorderColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the float thickness for the border of this point.
        /// Setting this property will also affect the BorderPen property.
        /// If this is negative or 0, the default layer thickness is used.
        /// </summary>
        public override float BorderWidth
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.BorderWidth = value;
            }
        }

        /// <summary>
        /// Gets or sets the pen to use with the this point to draw the border.
        /// Setting this will also reset the BorderColor proprety.
        /// </summary>
        public override System.Drawing.Pen BorderPen
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.BorderPen = value;
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that determines whether or not the default points for this layer
        /// will draw a border.  If this property is set to false, the other border properties will be ignored.
        /// </summary>
        public override bool BorderVisible
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.BorderVisible = value;
            }
        }

        /// <summary>
        /// If this string is open, returns Dimesions.Point, otherwise returns Dimensions.False
        /// </summary>
        public Dimensions BoundaryDimension
        {

            get
            {
                if (NumParts == 0) return Dimensions.False;
                return Dimensions.Point;
            }
        }
      
      
        /// <summary>
        /// This will only return the coordinate sequence for the first part.  If this 
        /// is a multi-linestring, then the coordinate sequence can be obtained from
        /// each IBasicLineString part in the list of parts.
        /// </summary>
        public ICoordinateSequence CoordinateSequence
        {
            get
            {
                if(Parts.Count == 0)return null;
                return _Parts[0].CoordinateSequence;
            }
        }

        /// <summary>
        /// Gets or sets the solid color to be used for filling this specific point.
        /// This will also change the FillBrush to a solid brush of the specified color.
        /// </summary>
        public override System.Drawing.Color FillColor
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.FillColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the System.Drawing.Brush to be used when filling this point.
        /// Setting this value will also change the FillColor property, but only if
        /// the brush is a System.Drawing.SolidBrush.  Otherwise, the color property
        /// will be ignored.
        /// </summary>
        public override System.Drawing.Brush FillBrush
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.FillBrush = value;
            }
        }
       

        /// <summary>
        /// Converts the double coordinates into an array of drawing points
        /// </summary>
        public virtual List<PointF[]> floatPointArrays
        {
            get
            {

                IBasicLineString ls;

                if (_floatPointArrays == null)
                {
                    // The Drawing functions require PointF, so calculate this once and then store it
                    // so we don't have to re-calculate this every time we need to draw the lines
                    _floatPointArrays = new List<PointF[]>();



                    // Linestrings can be multi-part so we have a list of parts, each stored as an array of points
                    for (int iPart = 0; iPart < _BasicGeometry.NumGeometries; iPart++)
                    {
                        int numPoints = _BasicGeometry.NumPoints;

                        // Set up an array to store the points
                        PointF[] temp = new PointF[numPoints];

                        // This should always be true, but testing it prevents a casting error if for some reason it isn't
                        if (BasicGeometry.GetBasicGeometryN(iPart) is IBasicLineString)
                        {
                            ls = ((IBasicLineString)BasicGeometry.GetBasicGeometryN(iPart));

                            // Each part of a linestring can have many points which form line segments.
                            // For closed linestrings, the end-point is the same as the startpoint, which
                            // is also necessary to close the drawing using DrawLines
                            for (int iPoint = 0; iPoint < numPoints; iPoint++)
                            {
                                temp[iPoint] = new PointF((float)ls.GetCoordinateN(iPoint).X, (float)ls.GetCoordinateN(iPoint).Y);
                            }
                        }
                        _floatPointArrays.Add(temp);

                    }


                }
                return _floatPointArrays;
            }
            set { _floatPointArrays = value; }
        }


        

        /// <summary>
        /// Gets or sets the LineSymbolizer that will be used when drawing
        /// this specific linear feature.
        /// </summary>
        protected ILineSymbolizer LineSymbolizer
        {
            get { return _LineSymbolizer; }
            set
            {
                _LineSymbolizer = value;
                _FeatureSymbolizer = (IFeatureSymbolizer)value;
            }
        }

        /// <summary>
        /// Gets or sets a float value from 0 to 1 where 0 is completely transparent
        /// and 1 is completely solid.  Setting an alpha of a specific feature, like
        /// FillColor, to something less than 255 will control that feature's transparency
        /// without affecting the others.  The final transparency of the feature will
        /// be that alpha multiplied by this value and rounded to the nearest byte.
        /// </summary>
        public override float Opacity
        {
            // Assume we previously referenced a PointLayer, and set the new
            // symbolizer to use the old one for default properties
            set
            {
                if (_IsEmpty == true)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.Opacity = value;
            }
        }


        /// <summary>
        /// Gets or sets an IMapLineStringLayer interface representing the layer this feature
        /// belongs to.
        /// </summary>
        public virtual IMapLineStringLayer ParentMapLineStringLayer
        {
            get { return _ParentMapLineStringLayer; }
            set { _ParentMapLineStringLayer = value; }
        }

        /// <summary>
        /// Gets or sets the list of pens to use when drawing this symbology
        /// </summary>
        public List<System.Drawing.Pen> FillPens
        {
            get { return _LineSymbolizer.FillPens; }
            set 
            {
                if (_IsEmpty)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false; 
                }
                _LineSymbolizer.FillPens = value;
            }
        }

        /// <summary>
        /// Gets an integer count representing the number of parts in this feature.
        /// </summary>
        public int NumParts
        {
            get { return _Parts.Count; }
        }

        /// <summary>
        /// Gets or sets the list of BasicLineString parts that make up the
        /// entire set of linestrings to draw with this feature.
        /// </summary>
        public List<IBasicLineString> Parts
        {
            get { return _Parts; }
            set 
            {
                if (value == null)
                {
                    _Parts = new List<IBasicLineString>();
                    _BasicGeometry = null;
                    return;
                }
                _Parts = value;
                if (_Parts.Count > 1)
                {
                    _BasicGeometry = new MultiLineString(_Parts.ToArray());
                }
                else
                {
                    _BasicGeometry = new LineString(_Parts[0]);
                }
            }
        }

        


        /// <summary>
        /// Gets or Sets a ScaleModes enumeration that determines whether non-coordinate drawing
        /// properties like width or size use pixels or world coordinates.  If pixels are
        /// specified, a back transform is used to approximate pixel sizes. 
        /// </summary>
        public override ScaleModes ScaleMode
        {
            // Assume we previously referenced a PointLayer, and set the new
            // symbolizer to use the old one for default properties
            set
            {
                if (_IsEmpty == true)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.ScaleMode = value;
            }
        }


        /// <summary>
        /// Gets or sets a boolean indicating whether or not this specific MapPoint should be drawn.
        /// </summary>
        public override bool Visible
        {
            // Assume we previously referenced a PointLayer, and set the new
            // symbolizer to use the old one for default properties
            set
            {
                if (_IsEmpty == true)
                {
                    LineSymbolizer = new LineSymbolizer(_LineSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.Visible = value;
            }
        }

       
      

       

        #endregion

      

    }
}
